# Manage disk space and files.
import time
import os
import threading
import stat
import popen2
from log import log
import sys

base = "/media/mmc2/podcasts"
# Always leave 100mb free on disk for a rainy day.
DISK_HEADROOM = 1024*1024*100

def GetCols(line):
  result = []
  while line:
    first_space = line.find(" ")
    if first_space == -1:
      result.append(line)
      return result
    result.append(line[:first_space])
    line = line[first_space:].lstrip()
  return result

def IsParent(base, dir):
  dir = os.path.realpath(dir)
  base = os.path.realpath(base)
  if os.path.commonprefix([dir, base]) == base:
    return len(base)
  else:
    return 0

def GetFreeSpace(base):
  #info = os.statvfs(dir)
  #return info[statvfs.F_BAVAIL] * info[statvfs.F_BSIZE]
  r, w = popen2.popen2("df -k", 10000)
  s = r.read()
  buf = ""
  while len(s):
    buf += s
    s = r.read()
  r.close()
  w.close()
  lines = buf.split("\n")
  longest = 0
  for line in lines[1:]:
    result = GetCols(line)
    if len(result) != 6:
      break
    filesystem, blocks, used, available, use_pct, dir = GetCols(line)
    parent_len = IsParent(dir, base)
    if parent_len > longest:
      longest = parent_len
      free_space = int(available)*1024
  if longest:
    return free_space
  class CouldNotFindDir(Exception): pass
  raise CouldNotFindDir()

def FullPath(relative_filename):
  global base
  return os.path.join(base, relative_filename)

def RelativePath(filename):
  assert filename[:len(base)+1] == base + "/"
  return filename[len(base)+1:]

def AllocateDiskSpace(relative_filename, size):
  global free_space
  global free_space_lock

  free_space_lock.acquire()
  if free_space < size:
    log("not enough space for",relative_filename)
    log("have",free_space,type(free_space))
    log("want",size,type(size))
    free_space_lock.release()
    return None
  else:
    free_space -= size
    log("Allocating",size,"bytes for",relative_filename,"leaving",free_space)
    free_space_lock.release()

  path = FullPath(relative_filename)
  Mkdir(os.path.split(path)[0])
  return path

def RemoveIncompleteFile(full_filename, reserved_size):
  global free_space
  global free_space_lock

  os.remove(full_filename)

  free_space_lock.acquire()
  free_space += reserved_size
  free_space_lock.release()

def Mkdir(path):
  if os.path.exists(path):
    return
  else:
    Mkdir(os.path.split(path)[0])
    os.mkdir(path)

def RemoveFile(filename):
  log("Removing file",FullPath(filename))
  actual_size = GetFileSize(FullPath(filename))
  os.remove(FullPath(filename))

  global free_space
  global free_space_lock
  free_space_lock.acquire()
  free_space += actual_size
  free_space_lock.release()

def Rmdir(dir):
  log("rmdir", dir)
  os.rmdir(dir)

def RecursiveRemoveDirectory(dir):
  log("rm -rf", dir)
  for file in os.listdir(dir):
    filename = os.path.join(dir, file)
    if stat.S_ISDIR(os.stat(filename)[stat.ST_MODE]):
      log(filename, "is dir")
      RecursiveRemoveDirectory(filename)
      Rmdir(filename)
    else:
      log(filename, "is file")
      RemoveFile(filename)
  Rmdir(dir)

def FileExists(filename):
  try:
    os.stat(FullPath(filename))
    return True
  except OSError:
    return False

def DeleteDanglingFeeds(feed_ids):
  global base

  log("DeleteDanglingFeeds: feeds are:", feed_ids)
  feed_dirs = os.listdir(base)
  for dir in feed_dirs:
    prefix, id = os.path.split(dir)
    try:
      if int(id) not in feed_ids:
        log(id, "not a valid feed. Removing files from", dir)
        RecursiveRemoveDirectory(os.path.join(base, dir))
    except ValueError:
      log(id, "got a valueerror--presuming it's a filename I shouldn't delete.")

def DeleteDanglingFiles(basedir, exists_cb):
  log("DeleteDanglingFiles called on", basedir)
  def DeleteDanglers(arg, dirname, filenames):
    log("DeleteDanglingFiles: dirname",dirname,"has files:",filenames)
    for filename in filenames:
      relpath = RelativePath(os.path.join(dirname, filename))
      if not exists_cb(relpath):
        log("%s dangling, deleting it." % relpath)
        RemoveFile(relpath)

  os.path.walk(FullPath(basedir), DeleteDanglers, None)

def GetFileSize(abs_filename):
  try:
    return os.stat(abs_filename)[stat.ST_SIZE]
  except OSError:
    # File probably doesn't exist
    log("Warning: GetFileSize of nonexistent file", abs_filename)
    return 0

Mkdir(base)
free_space = GetFreeSpace(base) - DISK_HEADROOM
log("Have", free_space, "bytes free")
free_space_lock = threading.Lock()
